<!--
* @author wn
* @date 2022/06/08 16:45:32
* @description: 商品规格 spu  sku 组件
* 路径字典对象   goods.skus  后台接口提供  幂集算法
* 是否禁用 可选  goods.specs  规格组件

* 向父组件传递数据 skuId 原价 现价 库存  购物车文字描述
* 接收默认选中 SKU

* 参考图片分析
!-->
<template>
	<div v-if="goods" class="goods-sku">
		<dl v-for="item in goods.specs" :key="item.name">
			<dt>{{ item.name }}</dt>
			<dd>
				<template v-for="i in item.values" :key="i.name">
					<img
						v-if="i.picture"
						:src="i.picture"
						:alt="i.name"
						:title="i.name"
						:class="{ selected: i.selected, disabled: i.disabled }"
						@click="changeSpecs(item, i)"
					/>
					<span
						v-else
						@click="changeSpecs(item, i)"
						:class="{ selected: i.selected, disabled: i.disabled }"
					>
						{{ i.name }}
					</span>
				</template>
			</dd>
		</dl>
	</div>
</template>
<script>
import powerSet from '@/vender/power-set'

export default {
	name: 'GoodSku',
	props: {
		goods: {
			type: Object,
			default: () => ({}),
		},
		skuId: {
			type: String,
			default: '',
		},
	},
	setup(props, { emit }) {
		// let a = 8
		// console.log(++a + a++ - --a + a-- + ++a)
		// *4 点击规格选项  为加入购物车 准备
		// item 每一行对象  i 改行内 单个 规格
		const changeSpecs = (item, i) => {
			// 如果 规格组件是禁用的  则不能选择
			if (i.disabled) return
			// 每个可选的规格都有一个属于自己的 selected 状态
			// 选中 就取消  没选中  先全部取消 再选中
			if (i.selected) {
				i.selected = false
			} else {
				// 排他思想，排除所有同级，当前自己高亮
				item.values.forEach(v => (v.selected = false))
				i.selected = true
			}
			// 点击规格选项 更新按钮禁用状态
			updateDisabledStatus(props.goods.specs, pathMap)
			/**
			 * 向父组件传递 选中的规格 数据 ==> id 原价 现价 库存  购物车文字描述
			 * 点击一次选中一次  只有 全部点击了
			 * 获取用户选中的字段 的个数  和 全部的规格个数相同 才是全不选择 才会 emit
			 */
			// 获取用户选中的字段 的数组  [undefined, undefined, undefined],["蓝色", "中国", "10cm"]
			const validSelectedSpecs = getSelectedArr(props.goods.specs).filter(
				v => v
			)
			// 获取全部的有效选择规格个数  和 全部的规格个数相同(规格全部选择上)
			if (validSelectedSpecs.length === props.goods.specs.length) {
				// 数组解构    pathMap-{}[key]->的value 是数组
				// 按顺序 取值 赋予名字  skuId取值 [0]
				const [skuId] = pathMap[validSelectedSpecs.join(split)]
				const { inventory, oldPrice, price, specs } = props.goods.skus.find(
					v => v.id === skuId
				)
				emit('change', {
					skuId,
					inventory,
					oldPrice,
					price,
					specsText: specs
						.reduce((p, v) => `${p} ${v.name}:${v.valueName}`, '')
						.trim(),
				})
			} else {
				// 不完整  不加入购物车  传递{}判断
				emit('change', {})
			}
		}
		// *1 获取有库存规格的 sku 所有情况字典
		const pathMap = getPathMap(props.goods.skus)
		// *2 初始化 更新按钮禁用状态
		updateDisabledStatus(props.goods.specs, pathMap)
		// *3 初始化默认选中规格-->购物车跳转过来的
		if (props.skuId) initSelectedStatus(props.goods, props.skuId)
		return { changeSpecs }
	},
}
/**
 * SPU   SKU
 * 生成 幂集 对象  key :所有规格组合 , value: skuID
 *
 * 根据有库存的规格，生成一个查询字典（用于后续用户实现选中)
 * 1 得到所有的SKU 集合  props.goods.skus
 * 2 从所有的SKU 集合中 ,筛选出 有效的SKU 有库存
 * 3 根据有效的SKU 通过 power-set算法 得到 幂集
 * 4 根据 幂集  向 路径字典对象中存储 key-value
 * 5 路径字典 value 为 skuID 是为了 计算不同规格的价格
 * goods.skus  后台接口提供
 * 最后生成的 对象 包含所有 排列方式
 * 但是全部规格选中的  只有 key★key★key:[1]
 */
const split = '★'
const getPathMap = skus => {
	// 路径字典对象
	const pathMap = {}
	// 获取到有库存的可选规格
	skus.forEach(sku => {
		// 如果有库存 有效的SKU
		if (sku.inventory > 0) {
			// 映射后结构：["蓝色", "中国", "10cm"]  子集1
			// 映射后结构：["红色", "中国", "10cm"]  子集2
			const valueNames = sku.specs.map(v => v.valueName)
			/**
			 *  幂集算法 - 获取该集合的所有子集
			 *  将每一条 sku 拆分成可以组合的最多方式  将所有组合生成pathMap
			 *  [[],[蓝色]，[中国],[10],[蓝色，中国],[蓝色,10],
			 *  [中国,10],["蓝色", "中国", "10cm"]]
			 *  子集1 的 幂集数组   skuId=sku.id = 1
			 *  遍历 子集2 会 追加 红色:[2] ,中国:[1,2] 10cm:[1,2] ["中国", "10cm"]:[1,2]
			 */
			const powerSetValues = powerSet(valueNames)
			// 生成 路径字典对象
			powerSetValues.forEach(v => {
				// 将数组转为字符串
				const key = v.join(split)
				if (pathMap[key]) {
					// 有key  数组中追加
					pathMap[key].push(sku.id)
				} else {
					// 没有key 直接赋值成数组
					pathMap[key] = [sku.id]
				}
			})
		}
	})
	return pathMap
}
/**
 * goods.specs  所有的规格
 * 获取用户选中的字段 [undefined, undefined, undefined],["蓝色", "中国", "10cm"]
 * 模拟全部点击完了  点击了  赋值 name 没点击  赋值 undefined
 * 主要靠 模拟全部点击完了--getSelectedArr 在 pathMap 中查询 逻辑
 */
const getSelectedArr = specs => {
	const selectedArr = []
	specs.forEach(spec => {
		// 每一个规格属性最多只有一个选中的 或者没有
		const selected = spec.values.find(v => v.selected)
		// 获取到选中字段的数组
		selectedArr.push(selected ? selected.name : undefined)
	})
	return selectedArr
}
/**
 * goods.specs
 * 初始化 或者 点击时   拿到 模拟全部点击完了 的数组 查询 pathMap 看是否有 value
 * 更新按钮禁用状态
 */
const updateDisabledStatus = (specs, pathMap) => {
	// 此处 spec循环完比 执行下一次 selectedArr数据赋值上一次的最后一个 没有清空
	// const selectedArr = getSelectedArr(specs)
	// 遍历规格最外层
	specs.forEach((spec, index) => {
		// 获取到当前被用户选中的 [undefined, undefined, undefined],["蓝色", "中国", "10cm"]
		const selectedArr = getSelectedArr(specs)
		// 遍历每一行 的 每一个 具体规格 对象
		spec.values.forEach(v => {
			// 1 如果当前被遍历的值是被选中的话 不用判断
			if (v.selected) return
			// 2 没有选中  安顺序 向数组中添加值   此处被赋值  循环完应该从新获取
			selectedArr[index] = v.name
			// 3 去掉undefined 拼接 key
			const key = selectedArr.filter(v => v).join(split)
			// 4 把模拟选中的规格去字典中查找是否存在，如果不存在，就设置为 disabled 禁用
			// 5 每个规格都设置一个 disabled 表示当前规格是否可选
			v.disabled = !pathMap[key]
		})
	})
}
// 初始化默认选中规格  由购物车指定 skuId
const initSelectedStatus = (goods, skuId) => {
	// 根据 skuId 获取到对应规格字段
	const sku = goods.skus.find(v => v.id === skuId)
	// 遍历 specs specs的item 和 sku中specs的item 一一对应
	sku &&
		goods.specs.forEach((item, i) => {
			// 找到需要选中的 规格  设置选中状态
			item.values.find(v => v.name === sku.specs[i].valueName).selected = true
		})
}
</script>
<style scoped lang="less">
.sku-state-mixin () {
	border: 1px solid #e4e4e4;
	margin-right: 10px;
	cursor: pointer;
	&.selected {
		border-color: @xtxColor;
	}
	&.disabled {
		opacity: 0.6;
		// 虚线
		border-style: dashed;
		cursor: not-allowed;
	}
}
.goods-sku {
	padding-left: 10px;
	padding-top: 20px;
	dl {
		display: flex;
		align-items: center;
		padding-bottom: 20px;
		dt {
			width: 50px;
			color: #999;
		}
		dd {
			color: #666;
			> img {
				width: 50px;
				height: 50px;
				.sku-state-mixin ();
			}
			> span {
				display: inline-block;
				height: 30px;
				line-height: 28px;
				padding: 0 20px;
				.sku-state-mixin ();
			}
		}
	}
}
</style>
